{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Zero-Noise Extrapolation\n",
    "\n",
    "*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Outline\n",
    "\n",
    "**Note: The number of credit points in the Quantum Hub account and time spent running this tutorial program will vary based on the parameters users input. Users need 28 points to obtain the results for the default parameters in this tutorial.  If you want to get more points, please contact us on [Quantum Hub](https://quantum-hub.baidu.com). First, you should log into [Quantum Hub](https://quantum-hub.baidu.com), then enter the \"Feedback\" page, choose \"Get Credit Point\", and input the necessary information. Submit your feedback and wait for a reply.**\n",
    "\n",
    "This tutorial introduces an efficient and general method for Quantum Error Mitigation: Zero-Noise Extrapolation (ZNE), covering its theory and implementation in Quanlse. We use the single-qubit random Clifford sequence as benchmark to illustrate how to use the ZNE method in Quanlse step-by-step. The outline of this tutorial is as follows:\n",
    "\n",
    "- ZNE: Theory\n",
    "    - Introduction\n",
    "    - Noise rescaling\n",
    "    - Extrapolation\n",
    "- ZNE: Practice\n",
    "    - Computation task description\n",
    "    - Quanlse implementation\n",
    "- Summary\n",
    "- Reference"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ZNE: Theory\n",
    "### Introduction\n",
    "Zero-Noise Extrapolation (ZNE) is a powerful technique for mitigating quantum errors in quantum computing. Notice that ZNE does not directly reduce the inherent noise in the quantum computing process, but instead infers the ideal computation result by repeating the same quantum computing process many times with different levels of noise \\[1, 2\\]. The advantage of ZNE is that we do not need to know the exact form of the noise as well as how to control the noise source.\n",
    "\n",
    "The implementation process of this method is shown in the figure below. The figure shows that the ZNE method is composed of two steps: rescaling noise and extrapolating. Among various noise rescaling techniques, time-variant rescaling is a robust and promising one. This technique stretches the system Hamiltonian in time domain according to some rescaling coefficient to obtain an equivalently noise-rescaled final quantum state. For simplicity, we use the Richardson extrapolation in our Quanlse implementation, a mature numeric algorithm that can eliminate error of any order in principle. We remark that there are many other extrapolation methods such as polynomial and exponential extrapolation methods \\[3\\].\n",
    "\n",
    "![zne-profile](figures/zne-profile.png)\n",
    "\n",
    "\n",
    "### Noise rescaling\n",
    "\n",
    "On the physical level, a quantum computing process with noise can be described by the Lindblad master equation:\n",
    "$$\n",
    "\\frac{\\partial}{\\partial t}\\rho(t) = -i[K,\\rho]+\\lambda\\mathcal{L}(\\rho),\n",
    "$$\n",
    "for time $t\\in[0,T]$. In this formulation, the Hamiltonian $K$ (which might be time-dependent) represents the ideal coherent evolution we aim to implement, while the Lindblad operator $\\mathcal{L}$ represents the noisy process we hope to mitigate. We emphasize that there is no need to know the exact form of the generator $\\mathcal{L}$. We only require that it is *time-invariant* and its effect is dominated by a scalar noise parameter $\\lambda$. Let $\\rho_\\lambda(T)$ be the final state after evolution time $T$. Given a positive coefficient $c$, can we obtain a noise-rescaled final state $\\rho_{c\\lambda}(T)$? Surprisingly, this is possible whenever the Lindblad operator is time-invariant. Consider the following procedure. We implement a time-stretching and amplitude-contracting version of the system Hamiltonian via:\n",
    "$$\n",
    "K(t)\\to K'(t) = \\frac{K(t/c)}{c}.\n",
    "$$\n",
    "What's more, we stretch the system evolution time to $cT$. It has been proven that this rescaled Hamiltonian $K'(t)$ will lead to a new evaluation whose final state is exactly $\\rho_{c\\lambda}(T)$ numerically \\[1\\].\n",
    "\n",
    "Experimentally, stretching the evolution time ($T\\to cT$) is easy to implement. Now let's analyze how to obtain the rescaled Hamiltonian $K'(t)$. In general, the systematic Hamiltonian is composed of time-independent drift items and time-dependent control ones, and the latter act on quantum states in the form of driving pulses. As an example, we learn from the [Single-Qubit Gate Tutorial](https://quanlse.baidu.com/#/doc/tutorial-single-qubit) in Quanlse that the driving pulses of the Hadamard gate\n",
    "$$\n",
    "H=\\frac{1}{\\sqrt{2}}\\begin{pmatrix}\n",
    "1&1\\\\\n",
    "1&-1\n",
    "\\end{pmatrix}\n",
    "$$\n",
    "are optimized as one $X$-channel pulse and one $Y$-channel pulse. As so, to implement the rescaled Hamiltonian is to stretch the corresponding driving pulses. In the following, we show by case the rescaled driving pulses of the optimized Hadamard gate with rescaling coefficients $1$ (does not rescale), $1.25$, and $1.5$.\n",
    "\n",
    "![zne-profile](figures/zne-pulse-rescale-h.png)\n",
    "\n",
    "To close this section, we comment that the noise parameter $\\lambda$ might also be other physical-relevant quantities, such as infidelity, temperature, error probability, variational parameter, etc. For example, we implement this ZNE method in Quanlse by treating the infidelity of the quantum circuit as the noise parameter $\\lambda$.\n",
    "\n",
    "### Extrapolation\n",
    "\n",
    "In numeric analysis, Richardson extrapolation is an efficient numerical method commonly used to eliminate low-order estimation errors. This method assumes that the estimated value $E(\\lambda)$ could be expressed as a power series of $\\lambda$ with respect to the ideal value $E^{\\ast}\\equiv E(\\lambda=0)$: \n",
    "$$\n",
    "E(\\lambda) = \\sum_{k=0}^{d} a_k\\lambda^k + O(\\lambda^{d+1}),\n",
    "$$\n",
    "where $E^{\\ast} = a_0$, $\\{a_k\\}$ is a set of coefficients to be determined, and $d$ is the order we aim to extrapolate. If we can obtain a set of estimators $\\left\\{E(\\lambda_j)\\right\\}_{j=1}^{d+1}$ with different parameters, we can construct a new estimator $E^d(\\lambda)$ from this set. In comparison with the original noisy estimator $E(\\lambda)$, this new estimator has a higher-precision estimation error (to $d$-order) \\[4\\].\n",
    "\n",
    "![extrapolation](figures/zne-extrapolation.png)\n",
    "\n",
    "In the above figure, we demonstrate the Richardson extrapolation by setting $d=2$. From the figure, we can see that the data points are linearly fitted, and the ideal value $E^{\\ast}$ can be inferred via extrapolation. It is worth noting that the Richardson extrapolation is just one of many extrapolation methods. It works well only when the power series assumption is valid. Luckily, this assumption holds naturally within the above Lindblad master equation framework, as justified in \\[1\\]."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ZNE: Practice"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Computation task description\n",
    "\n",
    "**Random Clifford circuit**\n",
    "\n",
    "A random Clifford circuit is a quantum circuit composed of randomly generated Clifford unitary gates, which has been intensively applied to benchmark the average error rates of quantum circuits. Here we consider the identity-equivalent single-qubit Clifford circuit composed of $n$ sequential random Clifford gates with the corresponding inverse gate attached to the end. As shown in the figure below, each $C_j$ is a randomly generated Clifford unitary gate while $C_{\\rm inv}$ is the inverse gate of all the preceding $n$ Clifford gates, that is,\n",
    "$$\n",
    "C_{\\rm inv}C_n C_{n-1}\\cdots C_1=I.\n",
    "$$\n",
    "\n",
    "**Computation task**\n",
    "\n",
    "\n",
    "Consider the following quantum computation task. The initial state is $|0\\rangle = \\begin{pmatrix} 1\\\\0\\end{pmatrix}$, the evolution circuit is an identity-equivalent Clifford circuit of size $n+1$, and the quantum observable is $A=|0\\rangle\\langle 0|=\\begin{pmatrix}1&0\\\\0&0 \\end{pmatrix}$.  \n",
    "\n",
    "![zne-clifford-circuit](figures/zne-clifford-circuit.png)\n",
    "\n",
    "Ideally, the final output quantum state will be $|0\\rangle$ since the evolution circuit is identity-equivalent. As so, the expectation value of $A$ will be $\\langle A\\rangle_{\\rm ideal}=1$, no matter how long the Clifford circuit is. \n",
    "\n",
    "However, due to the inevitable quantum noise when implementing the quantum circuit, the output state is no longer $|0\\rangle$, resulting in an incorrect expectation value \n",
    "$\\langle A\\rangle_{\\rm noisy}$. What's worse, the deeper the identity-equivalent quantum circuit is, the more that $\\langle A\\rangle_{\\rm noisy}$ deviates from the ideal value $1$. Notice that we compute the expectation value numerically after we obtain the final output state.\n",
    "\n",
    "In the following, we show that using the ZNE method offered by the Quanlse Cloud Service, we can mitigate the quantum noise dramatically, and the mitigated expectation value $\\langle A\\rangle_{\\rm miti}$ approaches the ideal value $\\langle A\\rangle_{\\rm ideal}$ for deep Clifford circuits. \n",
    "\n",
    "**Data processing procedure**\n",
    "\n",
    "We describe the data processing procedure in detail to fully reveal the power of the ZNE method implemented in Quanlse. For each $k=1,2,\\cdots,n$, we select the first $k$ gates of length $n$ Clifford sequence, compute the corresponding inverse gate, and construct the identity-equivalent circuit of length $k+1$. Then, for this circuit, we calculate the expectation value with the input state being $|0\\rangle$ and the quantum observable being $A$. We set the maximal extrapolation order to $d$ and compute the error-mitigated values of orders ranging from $1$ to $d$. Finally, we obtain $n\\times d$ extrapolated values and $n\\times (d+1)$ rescaling values."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Quanlse implementation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Import necessary modules and functions**\n",
    "\n",
    "To run the program below, you need to install [Quanlse](https://quanlse.baidu.com/#/doc/install) first. Then you need to import the following packages from Quanlse and some supporting Python libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "from Quanlse.remoteZNE import remoteZNEMitigation as zneMitigation\n",
    "from Quanlse.ErrorMitigation.ZNE.Extrapolation import extrapolate\n",
    "from Quanlse.ErrorMitigation.Utils.Utils import computeIdealExpectationValue, \\\n",
    "    computeIdealEvolutionOperator, fromCircuitToHamiltonian, randomCircuit, \\\n",
    "    computeInverseGate\n",
    "from Quanlse.ErrorMitigation.Utils.Visualization import plotZNESequences\n",
    "\n",
    "from Quanlse.Utils.Functions import project, expect\n",
    "from Quanlse.Utils.Infidelity import unitaryInfidelity\n",
    "\n",
    "from Quanlse.remoteSimulator import remoteSimulatorRunHamiltonian\n",
    "\n",
    "import numpy as np\n",
    "from copy import deepcopy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Usually, the zero-noise extrapolation method are computationally expensive. To deal with this issue, we provide our cloud service that could speed up this process significantly. To use the Quanlse Cloud Service, the users need to acquire a token from the [Quantum Leaf](http://quantum-hub.baidu.com) platform."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from Quanlse import Define\n",
    "\n",
    "Define.hubToken = ''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Construct random Clifford circuit**\n",
    "\n",
    "We use the built-in `randomCircuit` function to create a random Clifford sequence of length `numSeq`, whose data type is a `List` including a series of `CircuitLine` objects. Each `CircuitLine` describes a layer of the target quantum circuit. In this example, each layer consists of only one single-qubit gate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set the maximal length of the random Clifford circuit\n",
    "numSeq = 5\n",
    "numQubits = 1\n",
    "\n",
    "# Set the input state as |0> and the quantum observable as |0><0|\n",
    "state = np.diag([1, 0]).astype(complex)\n",
    "A = np.diag([1, 0]).astype(complex) \n",
    "\n",
    "# Set the maximal extrapolation order\n",
    "order = 2\n",
    "\n",
    "# Considering the reproducibility of our calculation result, we may as well set the \"random seed\" as a fixed value (e.g. 123)\n",
    "circuit = randomCircuit(qubits=1, numSeq=numSeq, seed=123)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Compute the ideal and noisy expectation values**\n",
    "\n",
    "For a quantum circuit of length $n$, we could use the built-in `computeInverseGate` function to calculate its inverse gate and then attach it to the end of the original quantum circuit. In this way, we construct an identity-equivalent quantum circuit totally including $n+1$ gates.\n",
    "\n",
    "Based on this quantum circuit and other initial parameters, we could compute both the ideal expectation value (via numerical simulation) and the noisy expectation value suffering from implementation error. For reference, we compute the infidelity between the ideal evolutionary operator and the noisy evolutionary operator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Construct the identity-equivalent quantum circuit by appending an inverse gate to the end\n",
    "circuitIdentity = circuit + [computeInverseGate(circuit)]\n",
    "\n",
    "# Compute the ideal expectation value (should be 1.0) and the ideal evolution operator (should be an identity operator)\n",
    "valueIdeal = computeIdealExpectationValue(state, circuitIdentity, A)\n",
    "unitaryIdeal = computeIdealEvolutionOperator(circuitIdentity)\n",
    "\n",
    "# Compute the optimized Hamiltonian for implementing the quantum circuit\n",
    "# The built-in Quanlse Scheduler will be called\n",
    "ham = fromCircuitToHamiltonian(circuitIdentity)\n",
    "\n",
    "# Use the given Hamiltonian to compute the implemented evolution unitary, the infidelity, and the noisy expectation value\n",
    "result = remoteSimulatorRunHamiltonian(ham)\n",
    "unitaryNoisy = project(result.result[0][\"unitary\"], ham.subSysNum, ham.sysLevel, 2)\n",
    "infid = unitaryInfidelity(unitaryIdeal, unitaryNoisy, numQubits)\n",
    "noisyValue = expect(A, unitaryNoisy @ state @ unitaryNoisy.conj().T)\n",
    "\n",
    "# Print the ideal and noisy expectation values\n",
    "print(\"The ideal expectation value: {}; The noisy expectation: {}\".format(valueIdeal, noisyValue))\n",
    "print(\"The ideal evolutionary operator:\")\n",
    "print(unitaryIdeal.round(3))\n",
    "print('The noisy evolutionary operator:')\n",
    "print(unitaryNoisy.round(3))\n",
    "print(\"The implemented evolution unitary has infidelity: \", infid)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Error mitigation via ZNE**\n",
    "\n",
    "There exists a deviation between the ideal expectation value and the noisy expectation value. As we have explained in the Theory section, ZNE is a feasible and efficient method to mitigate this kind of deviation.\n",
    "\n",
    "Using the built-in `extrapolate` function, we could calculate the mitigated expectation value from a set of rescaling coefficients and corresponding noise-rescaling values. In comparison with the original noisy expectation value, The mitigated expectation value has a higher estimation precision. In Quanlse, the ZNE method is implemented and is available via the `zneMitigation` interface. It includes both the noise-rescaling and the extrapolating procedures. `zneMitigation` returns a mitigated expectation value (to the $d$-th order), a set of infidelities (a list of $d+1$ real numbers), and a set of noisy expectation values of different noise levels (a list of $d+1$ real numbers).\n",
    "\n",
    "According to the data processing procedure described above, we need to execute the `zneMitigation` function for `numSeq` times. The process for optimizing the target Hamiltonian will perform `numSeq` times in total, which is computationally expensive. As so, we use the Quanlse Cloud Service to accelerate the optimizing process."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "EsRescaled = []        # EsRescaled size: [numSeq, order + 1]\n",
    "EsExtrapolated = []    # EsExtrapolated size: [numSeq, order]\n",
    "EsIdeal = []           # EsIdeal size: [numSeq,]\n",
    "Infidelities = []      # Infidelities size: [numSeq, order + 1]\n",
    "\n",
    "for length in range(1, numSeq + 1):\n",
    "    print('==' * 20)\n",
    "    print(\"Clifford circuit length:\", length)\n",
    "    # For each sequence, append the equivalent-inverse gate of all the preceding quantum gates\n",
    "    # For each sequence, its length becomes: [1, 2, ..., numSeq] + 1\n",
    "    circuitPart = deepcopy(circuit[:length])\n",
    "    lastGate = computeInverseGate(circuitPart)\n",
    "    circuitPart.append(lastGate)\n",
    "\n",
    "    # Compute ideal expectations firstly for subsequent comparison in figure\n",
    "    EsIdeal.append(computeIdealExpectationValue(state, circuitPart, A))\n",
    "\n",
    "    # Temporary extrapolated values of each order for each-length circuit\n",
    "    mitigatedValues = []\n",
    "    \n",
    "    # Use the Scheduler to compute the optimal Hamiltonian for this circuit\n",
    "    ham = fromCircuitToHamiltonian(circuitPart)\n",
    "\n",
    "    # Rescale order: [c_0, c_1, ..., c_d]; extrapolation order: d\n",
    "    mitigatedValueHighest, infidelities, noisyValues = zneMitigation(state, circuitPart, A, ham=ham, order=order)\n",
    "\n",
    "    # Rescale order: [c_0, c_1], [c_0, c_1, c_2], ...., [c_0, ..., c_{d-1}]\n",
    "    # for d in [1, ..., d - 1]:\n",
    "    for d in range(1, order):\n",
    "        mitigatedValue = extrapolate(infidelities[:(d + 1)], noisyValues[:(d + 1)], type='richardson', order=d)\n",
    "        mitigatedValues.append(mitigatedValue)\n",
    "\n",
    "    mitigatedValues.append(mitigatedValueHighest)\n",
    "\n",
    "    EsExtrapolated.append(mitigatedValues)\n",
    "    EsRescaled.append(noisyValues)\n",
    "    Infidelities.append(infidelities)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Result and discussion**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# X-axis represents length of quantum circuit, Y-axis represents expectation values\n",
    "plotZNESequences(EsRescaled, EsExtrapolated, EsIdeal, fileName='zne-single-qubit-clifford')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can tell from the figure, our noise-rescaling strategy *does* improve the precision of the estimated expectation value. What's more, the larger the rescaling coefficient is, the larger the resulting noisy expectation value bias. It anticipates that rescaling would lead to *worse* Hamiltonian for the quantum circuit implementation since the Hamiltonian optimized by Quanlse `Scheduler` is already the best. The power of extrapolation is self-evident as the precision of mitigated expectation values is improved significantly. Interestingly, just first-order or second-order extrapolation yield estimated expectation values could approach the ideal expectation to a great extent."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One might notice that in the above extrapolation plot, the $1$-order rescaled expectation values, which are obtained via the optimized Hamiltonians without rescaling, are very close to the ideal expectation value. It is because Quanlse can generate the single-qubit driving Hamiltonian with extremely high fidelity. To better illustrate the extrapolation technique, we compute the error mitigated values using only the $2$ and $3$-order rescaled expectation values. Remarkably, the mitigated expectation values are pretty close to the ideal expectation value, witnessing the power of the Richardson extrapolation method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "InfidelitiesPartial = np.array(Infidelities)[:, 1:]\n",
    "EsRescaledPartial = np.array(EsRescaled)[:, 1:]\n",
    "orderPartial = order - 1\n",
    "EsExtrapolatedPartial = []    # size: [numSeq, order + 1]\n",
    "for i in range(numSeq):\n",
    "    mitigatedValues = []\n",
    "    for d in range(1, orderPartial + 1):\n",
    "        mitigatedValue = extrapolate(InfidelitiesPartial[i][:(d + 1)], EsRescaledPartial[i][:(d + 1)], type='richardson', order=d)\n",
    "        mitigatedValues.append(mitigatedValue)\n",
    "    EsExtrapolatedPartial.append(mitigatedValues)\n",
    "\n",
    "plotZNESequences(EsRescaledPartial, EsExtrapolatedPartial, EsIdeal, fileName='zne-single-qubit-clifford-2')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "This tutorial describes how to use the Zero-Noise Extrapolation method implemented in Quanlse to improve the precision of quantum computation results by considering a representative example, random single-qubit Clifford circuits, as a benchmark. Interested users may click on this link [tutorial-ZNE.ipynb](https://github.com/baidu/Quanlse/blob/main/Tutorial/EN/tutorial-ZNE.ipynb) to jump to the corresponding GitHub page for this Jupyter Notebook documentation to get the code and try different parameters to further explore the power of the Quanlse ZNE module."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Reference\n",
    "\n",
    "\\[1\\] [Temme, K., et al. (2017). \"Error mitigation for short-depth quantum circuits.\" *Physical Review Letters* 119(18): 180509](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.180509).\n",
    "\n",
    "\\[2\\] [Kandala, A., et al. (2019). \"Error mitigation extends the computational reach of a noisy quantum processor.\" *Nature* 567(7749): 491-495](https://www.nature.com/articles/s41586-019-1040-7).\n",
    "\n",
    "\\[3\\] [Giurgica-Tiron, T., et al. (2020). \"Digital zero noise extrapolation for quantum error mitigation.\" 2020 IEEE International Conference on Quantum Computing and Engineering (QCE)](https://ieeexplore.ieee.org/document/9259940).\n",
    "\n",
    "\\[4\\] [A. Sidi (2003). \"Practical Extrapolation Methods: Theory and Applications.\" Cambridge Monographs on Applied and Computational Mathematics, Vol. 10](https://www.cambridge.org/core/books/practical-extrapolation-methods/21A93C2B0793CF09B2F3ABEF78F3F9B9)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
